﻿using System.Collections.Generic;
using System.Linq;
using JamesZhao.Engine.GameComponents;

namespace JamesZhao.Engine.GameObjects
{
    public class GameObject
    {
        private readonly List<GameComponent> _newGameComponents = new List<GameComponent>();
        private readonly List<GameObject> _newGameObjects = new List<GameObject>();
        private GameObject _parent;

        public GameObject(GameObject parent)
        {
            Children = new List<GameObject>();
            Components = new List<GameComponent>();
            AddDefaultComponents();

            Engine = parent.Engine;
            Parent = parent;
        }

        protected GameObject(GameEngine engine)
        {
            Children = new List<GameObject>();
            Components = new List<GameComponent>();
            AddDefaultComponents();

            Engine = engine;
            Parent = null;
        }

        public InputManager Input
        {
            get { return Engine.InputManager; }
        }

        public GameEngine Engine { get; protected set; }

        public GameObject Parent
        {
            get { return _parent; }
            internal set
            {
                if (_parent != value)
                {
                    if (_parent != null)
                    {
                        _parent.RemoveChild(this);
                    }
                    _parent = value;
                    _parent.AddChild(this);
                    OnParentChanged(_parent);
                    Transform.ParentTransform = _parent.Transform;
                }
            }
        }

        public ICollection<GameObject> Children { get; set; }
        public ICollection<GameComponent> Components { get; set; }

        public string Name { get; set; }
        public TransformComponent Transform { get; private set; }

        private void AddDefaultComponents()
        {
            Transform = new TransformComponent(this);
            Components.Add(Transform);
        }

        internal void AddChild(GameObject child)
        {
            Children.Add(child);
            OnChildAdded(child);
            _newGameObjects.Add(child);
        }

        internal void RemoveChild(GameObject child)
        {
            Children.Remove(child);
            OnChildRemoved(child);
        }

        internal void AddComponent(GameComponent gameComponent)
        {
            Components.Add(gameComponent);
            OnComponentAdded(gameComponent);
            _newGameComponents.Add(gameComponent);
        }

        internal void RemoveComponent(GameComponent gameComponent)
        {
            Components.Remove(gameComponent);
            OnComponentRemoved(gameComponent);
        }

        public T GetComponent<T>() where T : GameComponent
        {
            //note: uses "is". returns x if x is T OR if x inherits T
            return Components.First(x => x is T) as T;
        }

        public IEnumerable<T> GetAllComponents<T>() where T : GameComponent
        {
            //note: uses "is". returns x if x is T OR if x inherits T
            return Components.Where(x => x is T) as IEnumerable<T>;
        }

        protected virtual void OnChildAdded(GameObject child)
        {
        }

        protected virtual void OnChildRemoved(GameObject child)
        {
        }

        protected virtual void OnParentChanged(GameObject parent)
        {
        }

        protected virtual void OnComponentAdded(GameComponent gameComponent)
        {
        }

        protected virtual void OnComponentRemoved(GameComponent gameComponent)
        {
        }

        public virtual void Update()
        {
            UpdateComponents();
            UpdateChildren();
        }

        protected void UpdateChildren()
        {
            //TODO: the following should not happen here...
            foreach (GameObject newGameObject in _newGameObjects)
            {
                newGameObject.Initialize();
            }
            _newGameObjects.Clear();
            foreach (GameObject child in Children)
            {
                child.Update();
            }
        }

        protected void UpdateComponents()
        {
            //TODO: the following should not happen here...
            foreach (GameComponent newGameComponent in _newGameComponents)
            {
                newGameComponent.Initialize();
            }
            _newGameComponents.Clear();
            foreach (GameComponent component in Components)
            {
                component.Update();
            }
        }

        public virtual void Initialize()
        {
        }

        public virtual void Destroy()
        {
        }
    }

    internal class RootGameObject : GameObject
    {
        public RootGameObject(GameEngine engine) : base(engine)
        {
        }
    }
}